// balxml_encoder.h                                                   -*-C++-*-

// ----------------------------------------------------------------------------
//                                   NOTICE
//
// This component is not up to date with current BDE coding standards, and
// should not be used as an example for new development.
// ----------------------------------------------------------------------------

#ifndef INCLUDED_BALXML_ENCODER
#define INCLUDED_BALXML_ENCODER

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide an XML encoder utility.
//
//@CLASSES:
//  balxml::Encoder: XML encoder utility class
//
//@SEE_ALSO: balxml_decoder, balber_berencoder
//
//@DESCRIPTION: This component provides a class for encoding value-semantic
// objects in XML format.  In particular, the 'balxml::Encoder' 'class'
// contains a parameterized 'encode' function that encodes a specified
// value-semantic object into a specified stream.  There are three overloaded
// versions of this function:
//
//: o writes to an 'bsl::streambuf'
//: o writes to an 'bsl::ostream'
//: o writes to an 'balxml::Formatter'
//
// The 'encode' function encodes objects in XML format, which is a very useful
// format for debugging.  For more efficient performance, a binary encoding
// (such as BER) should be used.
//
// This component can be used with types supported by the 'bdlat' framework.
// In particular, types generated by the 'bas_codegen.pl' tool can be used.
//
///Usage
///-----
// The following snippets of code illustrate the usage of this component.
// Suppose we have an XML schema inside a file named 'employee.xsd':
//..
//  <?xml version='1.0' encoding='UTF-8'?>
//  <xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'
//             xmlns:test='http://bloomberg.com/schemas/test'
//             targetNamespace='http://bloomberg.com/schemas/test'
//             elementFormDefault='unqualified'>
//
//      <xs:complexType name='Address'>
//          <xs:sequence>
//              <xs:element name='street' type='xs:string'/>
//              <xs:element name='city'   type='xs:string'/>
//              <xs:element name='state'  type='xs:string'/>
//          </xs:sequence>
//      </xs:complexType>
//
//      <xs:complexType name='Employee'>
//          <xs:sequence>
//              <xs:element name='name'        type='xs:string'/>
//              <xs:element name='homeAddress' type='test:Address'/>
//              <xs:element name='age'         type='xs:int'/>
//          </xs:sequence>
//      </xs:complexType>
//  </xs:schema>
//..
// Using the 'bas_codegen.pl' tool, we generate C++ classes for this schema as
// follows:
//..
//  $ bas_codegen.pl -m msg -p test employee.xsd
//..
// This tool will generate the header and implementation files for the
// 'test_messages' components in the current directory.
//
// Now suppose we wanted to encode information about a particular employee
// using XML encoding to the standard output, and using the 'PRETTY' option for
// formatting the output.  The following function will do this:
//..
//  #include <test_messages.h>
//
//  #include <balxml_encoder.h>
//  #include <balxml_encodingstyle.h>
//
//  #include <bsl_iostream.h>
//  #include <bsl_sstream.h>
//
//  using namespace BloombergLP;
//
//  void usageExample()
//  {
//      test::Employee bob;
//
//      bob.name()                 = "Bob";
//      bob.homeAddress().street() = "Some Street";
//      bob.homeAddress().city()   = "Some City";
//      bob.homeAddress().state()  = "Some State";
//      bob.age()                  = 21;
//
//      balxml::EncoderOptions options;
//      options.setEncodingStyle(balxml::EncodingStyle::BAEXML_PRETTY);
//
//      balxml::Encoder encoder(&options, &bsl::cerr, &bsl::cerr);
//
//      const bsl::string EXPECTED_OUTPUT =
//       "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
//       "<Employee xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
//       "    <name>Bob</name>\n"
//       "    <homeAddress>\n"
//       "        <street>Some Street</street>\n"
//       "        <city>Some City</city>\n"
//       "        <state>Some State</state>\n"
//       "    </homeAddress>\n"
//       "    <age>21</age>\n"
//       "</Employee>\n";
//
//      bsl::ostringstream os;
//      const int rc = encoder.encodeToStream(os, bob);
//
//      assert(0 == rc);
//      assert(EXPECTED_OUTPUT == os.str());
//  }
//..

#include <balscm_version.h>

#include <balxml_encoderoptions.h>
#include <balxml_encodingstyle.h>
#include <balxml_errorinfo.h>      // for Severity
#include <balxml_formatter.h>
#include <balxml_typesprintutil.h>

#include <bdlat_arrayfunctions.h>
#include <bdlat_choicefunctions.h>
#include <bdlat_nullablevaluefunctions.h>
#include <bdlat_sequencefunctions.h>
#include <bdlat_typecategory.h>
#include <bdlat_typename.h>

#include <bdlsb_memoutstreambuf.h>

#include <bslma_allocator.h>
#include <bslma_default.h>
#include <bslma_usesbslmaallocator.h>

#include <bslmf_nestedtraitdeclaration.h>

#include <bsls_assert.h>
#include <bsls_objectbuffer.h>
#include <bsls_review.h>

#include <bsl_ostream.h>
#include <bsl_string.h>
#include <bsl_vector.h>

namespace BloombergLP {
namespace balxml {

class Encoder_Context;

                               // =============
                               // class Encoder
                               // =============

class Encoder {
    // This 'class' contains the parameterized 'encode' functions that encode
    // 'bdlat' types in XML format.

    // FRIENDS
    friend class Encoder_Context;

  private:
    // PRIVATE TYPES
    class MemOutStream : public bsl::ostream
    {
        // This class provides stream for logging using
        // 'bdlsb::MemOutStreamBuf' as a streambuf.  The logging stream is
        // created on demand, i.e., during the first attempt to log message.
        bdlsb::MemOutStreamBuf d_sb;

        // Not implemented:
        MemOutStream(const MemOutStream&);
        MemOutStream& operator=(const MemOutStream&);

      public:
        // CREATORS
        MemOutStream(bslma::Allocator *basicAllocator = 0);
            // Create a new stream using the optionally specified
            // 'basicAllocator'.

        virtual ~MemOutStream();
            // Destroy this stream and release memory back to the allocator.
            //
            // Although the compiler should generate this destructor
            // implicitly, xlC 8 breaks when the destructor is called by name
            // unless it is explicitly declared.

        // MANIPULATORS
        void reset();
            // Reset the internal streambuf to empty.

        // ACCESSORS
        const char *data() const;
            // Return a pointer to the memory containing the formatted values
            // formatted to this stream.  The data is not null-terminated
            // unless a null character was appended onto this stream.

        int length() const;
            // Return the length of the formatted data, including null
            // characters appended to the stream, if any.
    };

  private:
    // DATA
    const EncoderOptions            *d_options;        // held, not owned
    bslma::Allocator                *d_allocator;      // held, not owned

    bsls::ObjectBuffer<MemOutStream> d_logArea;
        // placeholder for MemOutStream

    MemOutStream                    *d_logStream;
        // if not zero, log stream was created at the moment of first logging
        // and must be destroyed

    ErrorInfo::Severity              d_severity;       // error severity

    bsl::ostream                    *d_errorStream;    // held, not owned
    bsl::ostream                    *d_warningStream;  // held, not owned

    // PRIVATE MANIPULATORS
    ErrorInfo::Severity logError(const char               *text,
                                 const bslstl::StringRef&  tag,
                                 int                       formattingMode,
                                 int                       index = -1);

    bsl::ostream& logStream();
        // Return the stream for logging.  Note the if stream has not been
        // created yet, it will be created during this call.

  public:
    // TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(Encoder, bslma::UsesBslmaAllocator);

    // CREATORS
    Encoder(const EncoderOptions *options, bslma::Allocator *basicAllocator);

    Encoder(const EncoderOptions *options,
            bsl::ostream         *errorStream   = 0,
            bsl::ostream         *warningStream = 0,
            bslma::Allocator     *basicAllocator = 0);
        // Construct a encoder object using the specified 'options'.  Errors
        // and warnings will be rendered to the optionally specified
        // 'errorStream' and 'warningStream' respectively.

    ~Encoder();
        // Destroy this object.  This destruction has no effect on objects
        // pointed-to by the pointers provided at construction.

    template <class TYPE>
    int encode(bsl::streambuf *buffer, const TYPE& object);
        // Encode the specified non-modifiable 'object' to the specified
        // 'buffer'.  Return 0 on success, and a non-zero value otherwise.
        // Note that the encoder will use encoder options, error and warning
        // streams specified at the construction time.

    template <class TYPE>
    int encodeToStream(bsl::ostream& stream, const TYPE& object);
        // Encode the specified non-modifiable 'object' to the specified
        // 'stream'.  Return 0 on success, and a non-zero value otherwise.
        // Note that the encoder will use encoder options, error and warning
        // streams specified at the construction time.

    template <class TYPE>
    bsl::ostream& encode(bsl::ostream& stream, const TYPE& object);
        // Encode the specified non-modifiable 'object' to the specified
        // 'stream'.  Return a reference to 'stream'.  If an encoding error is
        // detected, 'stream.fail()' will be true on return.  Note that the
        // encoder will use encoder options, error and warning streams
        // specified at the construction time.  IMPORTANT: The use of
        // 'stream.fail()' to communicate errors to the caller has two
        // consequences: 1) if 'stream' is the same as the 'errorStream'
        // passed to the constructor, then the error message may be suppressed
        // (because of the output/error stream becoming invalidated) and 2) it
        // is important to call 'stream.clear()' after testing the stream
        // state.  To avoid these issues, we recommend that you use use
        // 'encodeToStream', above, instead of this version of 'encode'.

    template <class TYPE>
    int encode(Formatter& formatter, const TYPE& object);
        // Encode the specified non-modifiable 'object' to the specified
        // 'formatter'.  Return 0 on success, and a non-zero value otherwise.
        // Note that encoder will use encoder options, error and warning
        // streams specified at the construction time.

    //ACCESSORS
    const EncoderOptions *options() const;
        // Return the encoder options.

    bool isCompact() const;
        // Return 'true' if the encoding style in the encoder options is
        // defined as 'EncodingStyle::BAEXML_COMPACT', and 'false' otherwise.

    bsl::ostream *errorStream() const;
        // Return pointer to the error stream.

    bsl::ostream *warningStream() const;
        // Return pointer to the warning stream.

    ErrorInfo::Severity  errorSeverity() const;
        // Return the severity of the most severe warning or error encountered
        // during the last call to the 'encode' method.  The severity is reset
        // each time 'encode' is called.

    bslstl::StringRef loggedMessages() const;
        // Return a string containing any error, warning, or trace messages
        // that were logged during the last call to the 'encode' method.  The
        // log is reset each time 'encode' is called.
};

// ---- Anything below this line is implementation specific.  Do not use.  ----

                           // ======================
                           // struct Encoder_Context
                           // ======================

class Encoder_Context {
    // This 'struct' contains state that is maintained during encoding.  It
    // also contains methods for switching between pretty formatting and
    // compact formatting, based on the encoding options.

    // DATA
    Formatter  *d_formatter;
    Encoder    *d_encoder;

    // NOT IMPLEMENTED
    Encoder_Context(const Encoder_Context& other);
    Encoder_Context& operator=(const Encoder_Context& other);

  public:
    // CREATORS
    Encoder_Context(Formatter *formatter, Encoder *encoder);

    // MANIPULATORS
    template <class NAME_TYPE, class VALUE_TYPE>
    void addAttribute(const NAME_TYPE& name, const VALUE_TYPE& value);

    template<class NAME_TYPE, class VALUE_TYPE>
    void addAttribute(const NAME_TYPE&  name,
                      const VALUE_TYPE& value,
                      int               formattingMode);

    template <class NAME_TYPE>
    void closeElement(const NAME_TYPE& name);

    void invalidate();

    ErrorInfo::Severity logError(const char               *text,
                                 const bslstl::StringRef&  tag,
                                 int                       formattingMode,
                                 int                       index = -1);

    template <class NAME_TYPE>
    void openElement(const NAME_TYPE& name);

    bsl::ostream& rawOutputStream();

    // ACCESSORS
    const EncoderOptions& encoderOptions() const;

    int status() const;
};

                  // =======================================
                  // struct Encoder_OptionsCompatibilityUtil
                  // =======================================

struct Encoder_OptionsCompatibilityUtil {
    // Component-private 'struct'.  Do not use.
    //
    // This struct provides a namespace for a suite of functions used to
    // compute the options for the underlying XML formatter used by the
    // encoder, given the encoder's options.

  private:
    // PRIVATE CLASS METHODS
    static int getFormatterInitialIndentLevel(
                                         const EncoderOptions& encoderOptions);
        // Return the value of the 'InitialIndentLevel' field for a
        // 'FormatterOptions' object that corresponds to the specified
        // 'encoderOptions', which is 0 if the 'EncodingStyle' of
        // 'encoderOptions' is 'EncodingStyle::e_COMPACT', and is the value of
        // 'InitialIndentLevel' of 'encoderOptions' otherwise.

    static int getFormatterSpacesPerLevel(
                                         const EncoderOptions& encoderOptions);
        // Return the value of the 'SpacesPerLevel' field for a
        // 'FormatterOptions' object that corresponds to the specified
        // 'encoderOptions', which is 0 if the 'EncodingStyle' of
        // 'encoderOptions' is 'EncodingStyle::e_COMPACT', and is the value of
        // 'SpacesPerLevel' of 'encoderOptions' otherwise.

    static int getFormatterWrapColumn(const EncoderOptions& encoderOptions);
        // Return the value of the 'WrapColumn' field for a
        // 'FormatterOptions' object that corresponds to the specified
        // 'encoderOptions', which is -1 if the 'EncodingStyle' of
        // 'encoderOptions' is 'EncodingStyle::e_COMPACT', and is the value of
        // 'WrapColumn' of 'encoderOptions' otherwise.

  public:
    // CLASS METHODS
    static void getFormatterOptions(
                                int                   *formatterIndentLevel,
                                int                   *formatterSpacesPerLevel,
                                int                   *formatterWrapColumn,
                                EncoderOptions        *formatterOptions,
                                const EncoderOptions&  encoderOptions);
        // Load to the specified 'formatterIndentLevel',
        // 'formatterSpacesPerLevel', and 'formatterWrapColumn', the number of
        // spaces to indent the first element in the XML document, the number
        // of spaces to use for indenting each level of nesting in the
        // document, and the maximum horizontal column number after which the
        // encoder should insert a line break, respectively, based on the
        // specified 'encoderOptions'.  Load to the specified
        // 'formatterOptions' the options that the formatter should use to emit
        // XML, based on the 'encoderOptions'.  The behavior is undefined
        // unless 'formatterOptions' has the default value.
};

                         // ==========================
                         // class Encoder_EncodeObject
                         // ==========================

class Encoder_EncodeObject {
    // Component-private class.  Do not use.
    //
    // This struct encodes an object *with* enclosing tags.  Compared to the
    // 'EncoderUtil_EncodeValue' class below, this class prefixes the value
    // with an opening tag, and suffixes the value with a closing tag.  In
    // pseudocode, this is equivalent to:
    //..
    //  openTag()
    //  Encoder_EncodeValue()
    //  closeTag()
    //..
    // There is an overloaded version of 'bsl::vector<char>' because, based on
    // the formatting mode, this class needs to switch between encoding the
    // value in a single tag (i.e., when using BASE64, TEXT, IS_LIST or HEX)
    // and encoding the value in multiple tags (i.e., when repetition is used).

    // PRIVATE TYPES
    struct CanBeListOrRepetition { };
    struct CanBeRepetitionOnly   { };

    // PRIVATE DATA MEMBERS
    Encoder_Context *d_context_p;

  public:
    // IMPLEMENTATION MANIPULATORS
    template <class TYPE>
    int executeImp(const TYPE&               object,
                   const bslstl::StringRef&  tag,
                   int                       formattingMode,
                   bdlat_TypeCategory::Array);

    template <class TYPE>
    int executeImp(const TYPE&                       object,
                   const bslstl::StringRef&          tag,
                   int                               formattingMode,
                   bdlat_TypeCategory::NullableValue);

    template <class TYPE>
    int executeImp(const TYPE&                     object,
                   const bslstl::StringRef&        tag,
                   int                             formattingMode,
                   bdlat_TypeCategory::DynamicType);

    template <class TYPE, class ANY_CATEGORY>
    int executeImp(const TYPE&              object,
                   const bslstl::StringRef& tag,
                   int                      formattingMode,
                   ANY_CATEGORY);

    int executeImp(const bsl::vector<char>&  object,
                   const bslstl::StringRef&  tag,
                   int                       formattingMode,
                   bdlat_TypeCategory::Array);

    template <class TYPE>
    int executeArrayListImp(const TYPE& object, const bslstl::StringRef& tag);

    template <class TYPE>
    int executeArrayRepetitionImp(const TYPE&              object,
                                  const bslstl::StringRef& tag,
                                  int                      formattingMode);

  private:
    // NOT IMPLEMENTED
    Encoder_EncodeObject(const Encoder_EncodeObject&);
    Encoder_EncodeObject& operator=(const Encoder_EncodeObject&);

  public:
    // CREATORS
    explicit Encoder_EncodeObject(Encoder_Context *context);

    // Using compiler generated destructor:
    //  ~Encoder_EncodeObject();

    // MANIPULATORS
    template <class TYPE, class INFO_TYPE>
    int operator()(const TYPE& object, const INFO_TYPE& info);

    template <class TYPE>
    int execute(const TYPE&              object,
                const bslstl::StringRef& tag,
                int                      formattingMode);
};

                         // =========================
                         // class Encoder_EncodeValue
                         // =========================

class Encoder_EncodeValue {
    // Component-private class.  Do not use.
    //
    // This class just encodes a value *without* any enclosing tags.

    // PRIVATE DATA MEMBERS
    Encoder_Context *d_context_p;

  public:
    // IMPLEMENTATION MANIPULATORS
    template <class TYPE>
    int executeImp(const TYPE&                  object,
                   int                          formattingMode,
                   bdlat_TypeCategory::Sequence);

    template <class TYPE>
    int executeImp(const TYPE&                object,
                   int                        formattingMode,
                   bdlat_TypeCategory::Choice);

    template <class TYPE>
    int executeImp(const TYPE&                     object,
                   int                             formattingMode,
                   bdlat_TypeCategory::DynamicType);

    template <class TYPE, class ANY_CATEGORY>
    int executeImp(const TYPE& object, int formattingMode, ANY_CATEGORY);

  private:
    // NOT IMPLEMENTED
    Encoder_EncodeValue(const Encoder_EncodeValue&);
    Encoder_EncodeValue& operator=(const Encoder_EncodeValue&);

  public:
    // CREATORS
    explicit Encoder_EncodeValue(Encoder_Context *context);

    // Using compiler generated destructor:
    //  ~Encoder_EncodeValue();

    // MANIPULATORS
    template <class TYPE, class INFO_TYPE>
    int operator()(const TYPE& object, const INFO_TYPE& info);

    template <class TYPE>
    int execute(const TYPE& object, int formattingMode);
};

                      // ===============================
                      // class Encoder_SequenceFirstPass
                      // ===============================

class Encoder_SequenceFirstPass {
    // Component private class.  Do not use.
    //
    // This class is used as the first pass when encoding elements of a
    // sequence.  It basically does two things:
    //     o encode elements with the
    //       'bdlat_FormattingMode::e_IS_ATTRIBUTE' flag using the
    //       'Formatter::addAttribute' method.
    //     o looks for an element with the
    //       'bdlat_FormattingMode::e_IS_SIMPLE_CONTENT' flag and, if
    //       found, provides accessors to obtain the 'id' of the element.
    //       Note that the behavior is undefined unless there is only one
    //       element with 'IS_SIMPLE_CONTENT' flag and, if this element exist,
    //       all other elements must have 'IS_ATTRIBUTE' flag.

    // PRIVATE DATA MEMBERS
    Encoder_Context   *d_context_p;        // held, not owned
    bool                      d_hasSubElements;   // true if an element with
                                                  // neither 'IS_ATTRIBUTE' nor
                                                  // 'IS_SIMPLE_CONTENT' is
                                                  // found
    bdlb::NullableValue<int>  d_simpleContentId;  // the 'id' of the element
                                                  // with 'IS_SIMPLE_CONTENT'
                                                  // flag, if found

  public:
    // IMPLEMENTATION MANIPULATORS
    template <class TYPE>
    int addAttributeImp(const TYPE&                       object,
                        const bslstl::StringRef&          name,
                        int                               formattingMode,
                        bdlat_TypeCategory::NullableValue);
    template <class TYPE>
    int addAttributeImp(const TYPE&                     object,
                        const bslstl::StringRef&        name,
                        int                             formattingMode,
                        bdlat_TypeCategory::DynamicType);
    template <class TYPE, class ANY_CATEGORY>
    int addAttributeImp(const TYPE&              object,
                        const bslstl::StringRef& name,
                        int                      formattingMode,
                        ANY_CATEGORY);
        // Add an attribute with the specified 'name', the value of the
        // specified 'object', using the specified 'formattingMode'.  Note that
        // the last argument is used for overloading purposes only.

    template <class TYPE>
    int addAttribute(const TYPE&              object,
                     const bslstl::StringRef& name,
                     int                      formattingMode);
        // Add an attribute with the specified 'name', the value of the
        // specified 'object', using the specified 'formattingMode'.

  private:
    // NOT IMPLEMENTED
    Encoder_SequenceFirstPass(const Encoder_SequenceFirstPass&);
    Encoder_SequenceFirstPass& operator=(const Encoder_SequenceFirstPass&);

  public:
    // CREATORS
    explicit Encoder_SequenceFirstPass(Encoder_Context *context);
        // Create a visitor for first pass for sequences.

    // Generated by compiler:
    //  ~Encoder_SequenceFirstPass();

    // MANIPULATORS
    template <class TYPE, class INFO_TYPE>
    int operator()(const TYPE& object, const INFO_TYPE& info);
        // Called back when an element is visited.

    // ACCESSORS
    const bool& hasSubElements() const;
        // Return true if a sub-element is found, and false otherwise.

    const bdlb::NullableValue<int>& simpleContentId() const;
        // Return a null value if there is no element with 'IS_SIMPLE_CONTENT'
        // flag, or a non-null value with the integer 'id' of the element
        // otherwise.
};

                      // ================================
                      // class Encoder_SequenceSecondPass
                      // ================================

class Encoder_SequenceSecondPass {
    // Component-private class.  Do not use.
    //
    // This class is used as the second pass when encoding elements of a
    // sequence.  It basically calls 'EncoderUtil_EncodeObject' for elements
    // that do not have 'IS_ATTRIBUTE' flag.  Note that the behavior is
    // undefined if there is an element with the 'IS_SIMPLE_CONTENT' flag.

    // DATA
    Encoder_EncodeObject d_encodeObjectFunctor;
        // functor used to encode sub-elements

    // NOT IMPLEMENTED
    Encoder_SequenceSecondPass(const Encoder_SequenceSecondPass&);
    Encoder_SequenceSecondPass& operator=(const Encoder_SequenceSecondPass&);

  public:
    // CREATORS
    explicit
    Encoder_SequenceSecondPass(Encoder_Context *context);
        // Create a visitor for the second pass for sequences.

    // Generated by compiler:
    //  ~Encoder_SequenceSecondPass();

    // MANIPULATORS
    template <class TYPE, class INFO_TYPE>
    int operator()(const TYPE& object, const INFO_TYPE& info);
        // Called back when an element is visited.
};

// ============================================================================
//                               PROXY CLASSES
// ============================================================================

                  // ========================================
                  // struct Encoder_EncodeObject_executeProxy
                  // ========================================

struct Encoder_EncodeObject_executeProxy {
    // Component-private struct.  Do not use.

    // DATA MEMBERS
    Encoder_EncodeObject *d_instance_p;
    const bslstl::StringRef       *d_tag_p;
    int                          d_formattingMode;

    // CREATORS

    // Creators have been omitted to allow simple static initialization of this
    // struct.

    // FUNCTIONS
    template <class TYPE>
    inline
    int operator()(const TYPE& object)
    {
        return d_instance_p->execute(object, *d_tag_p, d_formattingMode);
    }
};

                // ===========================================
                // struct Encoder_EncodeObject_executeImpProxy
                // ===========================================

struct Encoder_EncodeObject_executeImpProxy {
    // Component-private struct.  Do not use.

    // DATA
    Encoder_EncodeObject *d_instance_p;
    const bslstl::StringRef       *d_tag_p;
    int                          d_formattingMode;

    // CREATORS

    // Creators have been omitted to allow simple static initialization of this
    // struct.

    // FUNCTIONS
    template <class TYPE>
    inline
    int operator()(const TYPE&, bslmf::Nil)
    {
        BSLS_ASSERT_SAFE(0);
        return -1;
    }

    template <class TYPE, class ANY_CATEGORY>
    inline
    int operator()(const TYPE& object, ANY_CATEGORY category)
    {
        return d_instance_p->executeImp(object,
                                        *d_tag_p,
                                        d_formattingMode,
                                        category);
    }
};

                 // ==========================================
                 // struct Encoder_EncodeValue_executeImpProxy
                 // ==========================================

struct Encoder_EncodeValue_executeImpProxy {
    // Component-private struct.  Do not use.

    // DATA MEMBERS
    Encoder_EncodeValue *d_instance_p;
    int                         d_formattingMode;

    // CREATORS

    // Creators have been omitted to allow simple static initialization of this
    // struct.

    // FUNCTIONS
    template <class TYPE>
    inline
    int operator()(const TYPE&, bslmf::Nil)
    {
        BSLS_ASSERT_SAFE(0);
        return -1;
    }

    template <class TYPE, class ANY_CATEGORY>
    inline
    int operator()(const TYPE& object, ANY_CATEGORY category)
    {
        return d_instance_p->executeImp(object, d_formattingMode, category);
    }
};

             // ==================================================
             // struct Encoder_SequenceFirstPass_addAttributeProxy
             // ==================================================

struct Encoder_SequenceFirstPass_addAttributeProxy {
    // Component-private struct.  Do not use.

    // DATA MEMBERS
    Encoder_SequenceFirstPass *d_instance_p;
    const bslstl::StringRef            *d_name_p;
    int                               d_formattingMode;

    // CREATORS

    // Creators have been omitted to allow simple static initialization of this
    // struct.

    // FUNCTIONS
    template <class TYPE>
    inline
    int operator()(const TYPE& object)
    {
        return d_instance_p->addAttribute(object, *d_name_p, d_formattingMode);
    }
};

           // =====================================================
           // struct Encoder_SequenceFirstPass_addAttributeImpProxy
           // =====================================================

struct Encoder_SequenceFirstPass_addAttributeImpProxy {
    // Component-private struct.  Do not use.

    // DATA MEMBERS
    Encoder_SequenceFirstPass *d_instance_p;
    const bslstl::StringRef            *d_name_p;
    int                               d_formattingMode;

    // CREATORS

    // Creators have been omitted to allow simple static initialization of this
    // struct.

    // FUNCTIONS
    template <class TYPE>
    inline
    int operator()(const TYPE&, bslmf::Nil)
    {
        BSLS_ASSERT_SAFE(0);
        return -1;
    }

    template <class TYPE, class ANY_CATEGORY>
    inline
    int operator()(const TYPE& object, ANY_CATEGORY category)
    {
        return d_instance_p->addAttributeImp(object,
                                             *d_name_p,
                                             d_formattingMode,
                                             category);
    }
};
}  // close package namespace

// ============================================================================
//                            INLINE DEFINITIONS
// ============================================================================

namespace balxml {

                       // ------------------------------
                       // class BerEncoder::MemOutStream
                       // ------------------------------

inline
Encoder::MemOutStream::MemOutStream(bslma::Allocator *basicAllocator)
: bsl::ostream(0)
, d_sb(bslma::Default::allocator(basicAllocator))
{
    rdbuf(&d_sb);
}

// MANIPULATORS
inline
void Encoder::MemOutStream::reset()
{
    d_sb.reset();
}

// ACCESSORS
inline
const char *Encoder::MemOutStream::data() const
{
    return d_sb.data();
}

inline
int Encoder::MemOutStream::length() const
{
    return (int)d_sb.length();
}

                               // -------------
                               // class Encoder
                               // -------------

inline
bool Encoder::isCompact() const
{
    return EncodingStyle::e_COMPACT == d_options->encodingStyle();
}

inline
const EncoderOptions *Encoder::options() const
{
    return d_options;
}

inline
bsl::ostream *Encoder::errorStream() const
{
    return d_errorStream;
}

inline
bsl::ostream *Encoder::warningStream() const
{
    return d_warningStream;
}

inline
ErrorInfo::Severity Encoder::errorSeverity() const
{
    return d_severity;
}

inline
bslstl::StringRef Encoder::loggedMessages() const
{
    if (d_logStream) {
        return bslstl::StringRef(d_logStream->data(), d_logStream->length());
                                                                      // RETURN
    }
    return bslstl::StringRef();
}

inline
bsl::ostream& Encoder::logStream()
{
    if (0 == d_logStream) {
        d_logStream = new(d_logArea.buffer()) MemOutStream(d_allocator);
    }
    return *d_logStream;
}

template <class TYPE>
inline
int Encoder::encode(bsl::streambuf *buffer, const TYPE& object)
{
    int indentLevel    = 0;
    int spacesPerLevel = 0;
    int wrapColumn     = 0;

    EncoderOptions formatterEncoderOptions;
    Encoder_OptionsCompatibilityUtil::getFormatterOptions(
        &indentLevel,
        &spacesPerLevel,
        &wrapColumn,
        &formatterEncoderOptions,
        *d_options);

    Formatter formatter(buffer,
                        formatterEncoderOptions,
                        indentLevel,
                        spacesPerLevel,
                        wrapColumn);

    const int rc = encode(formatter, object);

    buffer->pubsync();

    return rc;
}

template <class TYPE>
inline
int Encoder::encodeToStream(bsl::ostream& stream, const TYPE& object)
{
    return encode(stream.rdbuf(), object);
}

template <class TYPE>
inline
bsl::ostream& Encoder::encode(bsl::ostream& stream, const TYPE& object)
{
    int indentLevel    = 0;
    int spacesPerLevel = 0;
    int wrapColumn     = 0;

    EncoderOptions formatterEncoderOptions;
    Encoder_OptionsCompatibilityUtil::getFormatterOptions(
        &indentLevel,
        &spacesPerLevel,
        &wrapColumn,
        &formatterEncoderOptions,
        *d_options);

    Formatter formatter(stream,
                        formatterEncoderOptions,
                        indentLevel,
                        spacesPerLevel,
                        wrapColumn);

    encode(formatter, object);

    stream.flush();

    return stream;
}

template <class TYPE>
int Encoder::encode(Formatter& formatter, const TYPE& object)
{
    d_severity = ErrorInfo::e_NO_ERROR;
    if (d_logStream != 0) {
        d_logStream->reset();
    }

    Encoder_Context context(&formatter,this);

    if (d_options->outputXMLHeader()) {
        formatter.addHeader();
    }

    const char *tag = d_options->tag().empty()
                    ? bdlat_TypeName::xsdName(object,
                                              d_options->formattingMode())
                    : d_options->tag().c_str();

    context.openElement(tag);

    if (!d_options->objectNamespace().empty()) {

        context.addAttribute("xmlns", d_options->objectNamespace());

        if (d_options->outputXSIAlias()) {
            // Only declare the "xsi" namespace and schema location if an
            // object namespace was provided because only then can validation
            // happen.
            context.addAttribute("xmlns:xsi",
                                 "http://www.w3.org/2001/XMLSchema-instance");

            if (!d_options->schemaLocation().empty()) {
                context.addAttribute("xsi:schemaLocation",
                                     d_options->objectNamespace()
                                     + " "
                                     + d_options->schemaLocation());
            }
        }
    }
    else if (d_options->outputXSIAlias()) {
        context.addAttribute("xmlns:xsi",
                             "http://www.w3.org/2001/XMLSchema-instance");
    }

    Encoder_EncodeValue encodeValue(&context);

    int rc = 0;
    if (0 != encodeValue.execute(object,d_options->formattingMode())) {

        logError("Failed to encode", tag, d_options->formattingMode());

        context.invalidate();
        rc = -1;
    }
    else {
        context.closeElement(tag);
    }

    switch (d_severity) {
      case ErrorInfo::e_NO_ERROR: {
      } break;
      case ErrorInfo::e_WARNING: {
        if (d_warningStream) {
            *d_warningStream << loggedMessages();
        }
      } break;
      default: {
        if (d_errorStream) {
            *d_errorStream << loggedMessages();
        }
      } break;
    }
    return rc;
}

                           // ---------------------
                           // class Encoder_Context
                           // ---------------------

// MANIPULATORS
template <class NAME_TYPE, class VALUE_TYPE>
inline
void Encoder_Context::addAttribute(const NAME_TYPE&  name,
                                   const VALUE_TYPE& value)
{
    d_formatter->addAttribute(name,
                              value,
                              bdlat_FormattingMode::e_DEFAULT);
}

template <class NAME_TYPE, class VALUE_TYPE>
inline
void Encoder_Context::addAttribute(const NAME_TYPE&  name,
                                   const VALUE_TYPE& value,
                                   int               formattingMode)
{
    d_formatter->addAttribute(name, value, formattingMode);
}

template <class NAME_TYPE>
inline
void Encoder_Context::closeElement(const NAME_TYPE& name)
{
    d_formatter->closeElement(name);
}

inline
void Encoder_Context::invalidate()
{
    rawOutputStream().setstate(bsl::ios_base::failbit);
}

inline
ErrorInfo::Severity Encoder_Context::logError(
                                      const char               *text,
                                      const bslstl::StringRef&  tag,
                                      int                       formattingMode,
                                      int                       index)
{
    return d_encoder->logError(text, tag, formattingMode, index);
}

template <class NAME_TYPE>
inline
void Encoder_Context::openElement(const NAME_TYPE& name)
{
    d_formatter->openElement(name);
}

inline
bsl::ostream& Encoder_Context::rawOutputStream()
{
    return d_formatter->rawOutputStream();
}

// ACCESSORS
inline
const EncoderOptions& Encoder_Context::encoderOptions() const
{
    return *d_encoder->options();
}

inline
int Encoder_Context::status() const
{
    return d_formatter->status();
}

                         // --------------------------
                         // class Encoder_EncodeObject
                         // --------------------------

// IMPLEMENTATION MANIPULATORS
template <class TYPE>
inline
int Encoder_EncodeObject::executeImp(const TYPE&               object,
                                     const bslstl::StringRef&  tag,
                                     int                       formattingMode,
                                     bdlat_TypeCategory::Array)
{
    if (formattingMode & bdlat_FormattingMode::e_LIST) {
        return executeArrayListImp(object, tag);                      // RETURN
    }
    // else { return ... } removed, to prevent warning with gcc-4.1.1 (reach
    // end of non-void function), instead, have unconditional:

    return executeArrayRepetitionImp(object, tag, formattingMode);
}

template <class TYPE>
inline
int Encoder_EncodeObject::executeImp(
                              const TYPE&                       object,
                              const bslstl::StringRef&          tag,
                              int                               formattingMode,
                              bdlat_TypeCategory::NullableValue)
{
    enum { k_SUCCESS = 0 };

    if (bdlat_NullableValueFunctions::isNull(object)) {
        if (formattingMode & bdlat_FormattingMode::e_NILLABLE) {
            if (!d_context_p->encoderOptions().objectNamespace().empty()
             && d_context_p->encoderOptions().outputXSIAlias()) {
                // Only add the "xsi:nil" attribute if an object namespace was
                // provided because only then can validation happen.
                d_context_p->openElement(tag);
                d_context_p->addAttribute("xsi:nil", "true");
                d_context_p->closeElement(tag);
            }
        }

        return d_context_p->status();                                 // RETURN
    }

    Encoder_EncodeObject_executeProxy proxy = {
        this,
        &tag,
        formattingMode
    };

    return bdlat_NullableValueFunctions::accessValue(object, proxy);
}

template <class TYPE>
inline
int Encoder_EncodeObject::executeImp(
                                const TYPE&                     object,
                                const bslstl::StringRef&        tag,
                                int                             formattingMode,
                                bdlat_TypeCategory::DynamicType)
{
    Encoder_EncodeObject_executeImpProxy proxy = {
        this,
        &tag,
        formattingMode
    };

    return bdlat_TypeCategoryUtil::accessByCategory(object, proxy);
}

template <class TYPE, class ANY_CATEGORY>
int Encoder_EncodeObject::executeImp(const TYPE&              object,
                                     const bslstl::StringRef& tag,
                                     int                      formattingMode,
                                     ANY_CATEGORY)
{
    enum { k_FAILURE = -1 };

    bool isUntagged = formattingMode & bdlat_FormattingMode::e_UNTAGGED;

    if (!isUntagged) {
        d_context_p->openElement(tag);
    }

    Encoder_EncodeValue encodeValue(d_context_p);

    if (0 != encodeValue.execute(object, formattingMode)) {
        d_context_p->logError("Unable to encode value", tag, formattingMode);
        return k_FAILURE;                                             // RETURN
    }

    if (!isUntagged) {
        d_context_p->closeElement(tag);
    }

    int ret = d_context_p->status();

    if (ret) {
        d_context_p->logError("Formatter was invalidated for",
                              tag,
                              formattingMode);
    }

    return ret;
}

template <class TYPE>
int Encoder_EncodeObject::executeArrayListImp(const TYPE&              object,
                                              const bslstl::StringRef& tag)
{
    d_context_p->openElement(tag);

    TypesPrintUtil::printList(d_context_p->rawOutputStream(),
                              object,
                              &d_context_p->encoderOptions());

    d_context_p->closeElement(tag);

    int ret = d_context_p->status();

    if (ret) {

        d_context_p->logError(
            "Error while encoding list for",
            tag,
            EncoderOptions::DEFAULT_INITIALIZER_FORMATTING_MODE);
    }

    return ret;
}

template <class TYPE>
int Encoder_EncodeObject::executeArrayRepetitionImp(
                                       const TYPE&              object,
                                       const bslstl::StringRef& tag,
                                       int                      formattingMode)
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

    const int size = (int)bdlat_ArrayFunctions::size(object);

    Encoder_EncodeObject_executeProxy proxy = { this, &tag, formattingMode };

    for (int i = 0; i < size; ++i) {
        if (0 != bdlat_ArrayFunctions::accessElement(object, proxy, i)) {

            d_context_p->logError(
                "Error while encoding array element",
                tag,
                formattingMode,
                i);

            return k_FAILURE;                                         // RETURN
        }
    }

    return k_SUCCESS;
}

// CREATORS
inline
Encoder_EncodeObject::Encoder_EncodeObject(Encoder_Context *context)
: d_context_p(context)
{
    BSLS_ASSERT(d_context_p);
}

// MANIPULATORS
template <class TYPE, class INFO_TYPE>
inline
int Encoder_EncodeObject::operator()(const TYPE& object, const INFO_TYPE& info)
{
    bslstl::StringRef name(info.name(), info.nameLength());

    return execute(object, name, info.formattingMode());
}

template <class TYPE>
inline
int Encoder_EncodeObject::execute(const TYPE&              object,
                                  const bslstl::StringRef& tag,
                                  int                      formattingMode)
{
    typedef typename bdlat_TypeCategory::Select<TYPE>::Type TypeCategory;

    return executeImp(object, tag, formattingMode, TypeCategory());
}

                         // -------------------------
                         // class Encoder_EncodeValue
                         // -------------------------

// IMPLEMENTATION MANIPULATORS
template <class TYPE>
inline
int Encoder_EncodeValue::executeImp(
                                   const TYPE&                  object,
                                   int                          formattingMode,
                                   bdlat_TypeCategory::Sequence)
{
    enum { k_SUCCESS = 0, k_FAILURE = -1 };

#if defined(BSLS_ASSERT_SAFE_IS_ACTIVE)
    int type = formattingMode & bdlat_FormattingMode::e_TYPE_MASK;

    BSLS_ASSERT_SAFE(bdlat_FormattingMode::e_DEFAULT == type);
#else
    (void) formattingMode;
#endif

    Encoder_SequenceFirstPass firstPass(d_context_p);

    if (0 != bdlat_SequenceFunctions::accessAttributes(object, firstPass)) {
        return k_FAILURE;                                             // RETURN
    }

    if (!firstPass.simpleContentId().isNull()) {
        Encoder_EncodeValue encodeValue(d_context_p);

        return bdlat_SequenceFunctions::accessAttribute(
                                          object,
                                          encodeValue,
                                          firstPass.simpleContentId().value());
                                                                      // RETURN
    }

    if (firstPass.hasSubElements()) {
        Encoder_SequenceSecondPass secondPass(d_context_p);

        return bdlat_SequenceFunctions::accessAttributes(object, secondPass);
                                                                      // RETURN
    }

    return k_SUCCESS;
}

template <class TYPE>
inline
int Encoder_EncodeValue::executeImp(const TYPE&                object,
                                    int                        formattingMode,
                                    bdlat_TypeCategory::Choice)
{
    enum { k_FAILURE = -1 };

#if defined(BSLS_ASSERT_SAFE_IS_ACTIVE)
    int type = formattingMode & bdlat_FormattingMode::e_TYPE_MASK;

    BSLS_ASSERT_SAFE(bdlat_FormattingMode::e_DEFAULT == type);
#endif

    if (bdlat_ChoiceFunctions::k_UNDEFINED_SELECTION_ID
                               == bdlat_ChoiceFunctions::selectionId(object)) {

        d_context_p->logError("Undefined selection is not allowed ",
                              "???",
                              formattingMode);
        return k_FAILURE;                                             // RETURN
    }

    Encoder_EncodeObject encodeObject(d_context_p);

    return bdlat_ChoiceFunctions::accessSelection(object, encodeObject);
}

template <class TYPE>
inline
int Encoder_EncodeValue::executeImp(
                                const TYPE&                     object,
                                int                             formattingMode,
                                bdlat_TypeCategory::DynamicType)
{
    Encoder_EncodeValue_executeImpProxy proxy = { this, formattingMode };

    return bdlat_TypeCategoryUtil::accessByCategory(object, proxy);
}

template <class TYPE, class ANY_CATEGORY>
inline
int Encoder_EncodeValue::executeImp(const TYPE&  object,
                                    int          formattingMode,
                                    ANY_CATEGORY)
{
    TypesPrintUtil::print(d_context_p->rawOutputStream(),
                          object,
                          formattingMode,
                          &d_context_p->encoderOptions());

    return d_context_p->status();
}

// CREATORS
inline
Encoder_EncodeValue::Encoder_EncodeValue(Encoder_Context *context)
: d_context_p(context)
{
    BSLS_ASSERT(d_context_p);
}

// MANIPULATORS
template <class TYPE, class INFO_TYPE>
inline
int Encoder_EncodeValue::operator()(const TYPE& object, const INFO_TYPE& info)
{
    typedef typename bdlat_TypeCategory::Select<TYPE>::Type TypeCategory;

    return executeImp(object, info.formattingMode(), TypeCategory());
}

template <class TYPE>
inline
int Encoder_EncodeValue::execute(const TYPE& object, int formattingMode)
{
    typedef typename bdlat_TypeCategory::Select<TYPE>::Type TypeCategory;

    return executeImp(object, formattingMode, TypeCategory());
}

                      // -------------------------------
                      // class Encoder_SequenceFirstPass
                      // -------------------------------

// IMPLEMENTATION MANIPULATORS
template <class TYPE>
inline
int Encoder_SequenceFirstPass::addAttributeImp(
                              const TYPE&                       object,
                              const bslstl::StringRef&          name,
                              int                               formattingMode,
                              bdlat_TypeCategory::NullableValue)
{
    enum { k_SUCCESS = 0 };

    if (bdlat_NullableValueFunctions::isNull(object)) {
        return k_SUCCESS;                                             // RETURN
    }

    Encoder_SequenceFirstPass_addAttributeProxy proxy = {
        this,
        &name,
        formattingMode
    };

    return bdlat_NullableValueFunctions::accessValue(object, proxy);
}

template <class TYPE>
inline
int Encoder_SequenceFirstPass::addAttributeImp(
                                const TYPE&                     object,
                                const bslstl::StringRef&        name,
                                int                             formattingMode,
                                bdlat_TypeCategory::DynamicType)
{
    Encoder_SequenceFirstPass_addAttributeImpProxy proxy = {
        this,
        &name,
        formattingMode
    };

    return bdlat_TypeCategoryUtil::accessByCategory(object, proxy);

}

template <class TYPE, class ANY_CATEGORY>
inline
int Encoder_SequenceFirstPass::addAttributeImp(
                                       const TYPE&              object,
                                       const bslstl::StringRef& name,
                                       int                      formattingMode,
                                       ANY_CATEGORY)
{
    d_context_p->addAttribute(name, object, formattingMode);

    int ret = d_context_p->status();

    if (ret) {
        d_context_p->logError("Failed to encode attribute",
                              name,
                              formattingMode);
    }

    return ret;
}

template <class TYPE>
inline
int Encoder_SequenceFirstPass::addAttribute(
                                       const TYPE&              object,
                                       const bslstl::StringRef& name,
                                       int                      formattingMode)
{
    typedef typename bdlat_TypeCategory::Select<TYPE>::Type TypeCategory;

    return addAttributeImp(object, name, formattingMode, TypeCategory());
}

// CREATORS
inline
Encoder_SequenceFirstPass::Encoder_SequenceFirstPass(Encoder_Context *context)
: d_context_p(context)
, d_hasSubElements(false)
{
    BSLS_ASSERT(d_context_p);
    BSLS_ASSERT(d_simpleContentId.isNull());

    // {DRQS 153551134<GO>}: gcc can occasionally mis-diagnose
    // 'd_simpleContentId' as uninitialized.  This workaround avoids that
    // problem (which can cause build failures if '-Wmaybe-uninitialized' and
    // '-Werror' are set).  See also {DRQS 75130685<GO>} and {DRQS
    // 115347303<GO>}.
    d_simpleContentId.makeValue(0);
    d_simpleContentId.reset();
}

// MANIPULATORS
template <class TYPE, class INFO_TYPE>
int Encoder_SequenceFirstPass::operator()(const TYPE&      object,
                                          const INFO_TYPE& info)
{
    enum { k_SUCCESS = 0 };

    int  formattingMode  = info.formattingMode();
    bool isSimpleContent = formattingMode
                         & bdlat_FormattingMode::e_SIMPLE_CONTENT;
    bool isAttribute     = formattingMode & bdlat_FormattingMode::e_ATTRIBUTE;

    if (isSimpleContent) {
        BSLS_ASSERT(!isAttribute);
        BSLS_ASSERT(!d_hasSubElements);
        BSLS_ASSERT(d_simpleContentId.isNull());

        d_simpleContentId.makeValue(info.id());
    }
    else if (isAttribute) {
        bslstl::StringRef name(info.name(), info.nameLength());

        return addAttribute(object, name, formattingMode);            // RETURN
    }
    else {
        BSLS_ASSERT(d_simpleContentId.isNull());

        d_hasSubElements = true;
    }

    return k_SUCCESS;
}

// ACCESSORS
inline
const bool& Encoder_SequenceFirstPass::hasSubElements() const
{
    return d_hasSubElements;
}

inline
const bdlb::NullableValue<int>&
Encoder_SequenceFirstPass::simpleContentId() const
{
    return d_simpleContentId;
}

                      // --------------------------------
                      // class Encoder_SequenceSecondPass
                      // --------------------------------

// CREATORS
inline
Encoder_SequenceSecondPass::Encoder_SequenceSecondPass(
                                                      Encoder_Context* context)
: d_encodeObjectFunctor(context)
{
}

// MANIPULATORS
template <class TYPE, class INFO_TYPE>
int Encoder_SequenceSecondPass::operator()(const TYPE&      object,
                                           const INFO_TYPE& info)
{
    enum { k_SUCCESS = 0 };

    int formattingMode = info.formattingMode();

    BSLS_ASSERT(
               !(formattingMode & bdlat_FormattingMode::e_SIMPLE_CONTENT));

    if (!(formattingMode & bdlat_FormattingMode::e_ATTRIBUTE)) {
        return d_encodeObjectFunctor(object, info);                   // RETURN
    }

    return k_SUCCESS;
}

}  // close package namespace
}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2015 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------- END-OF-FILE ----------------------------------
